﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;

namespace FontConverter
{
    public partial class Form1 : Form
    {
        private InstalledFontCollection mFonts;
        private FontFamily mSelectedFamily;
        private Font mSelectedFont;
        private List<Bitmap> mBitmaps;

        private Dictionary<int, List<int>> mSizeMap;
        private Dictionary<int, Bitmap> mCharMap;
        private Dictionary<int, PointF> mCharOfs;
        private Dictionary<int, SizeF> mCharSize;
        private Dictionary<int, RectangleF> mCharLoc;
        private Dictionary<int, int> mCharTex;
        private List<Bitmap> mTextures;
        private int mBaseline;
        private string mCurrentPath;

        public Form1()
        {
            InitializeComponent();

            fontInfo1.FontList.Clear();
            mFonts = new InstalledFontCollection();
            foreach (FontFamily family in mFonts.Families)
            {
                FontStyle? style = FindStyle(family);
                if (style == null) continue;

                FontInfo.FontListRow row = fontInfo1.FontList.NewFontListRow();
                row.FontName = family.Name;
                row.FontPreview = "ABCDE abcde 12345 !@#$%^&*()";
                fontInfo1.FontList.AddFontListRow(row);
            }

            Form1_Resize(null, null);

            mBitmaps = new List<Bitmap>();

            comboBox1.SelectedIndex = 0;

            mCurrentPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        }

        private void preferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            DataRowView view = (DataRowView)dataGridView1.Rows[e.RowIndex].DataBoundItem;
            FontInfo.FontListRow row = (FontInfo.FontListRow)view.Row;
            if (e.ColumnIndex == 0)
            {
                e.CellStyle.Font = new Font(e.CellStyle.Font.FontFamily.Name, 20, FontStyle.Regular);
            }
            if (e.ColumnIndex == 1)
            {
                FontFamily family = null;
                foreach (FontFamily tmp in mFonts.Families)
                {
                    if (tmp.Name == row.FontName)
                    {
                        family = tmp;
                        break;
                    }
                }
                FontStyle? style = FindStyle(family);
                e.CellStyle.Font = new Font(family, 20, style.Value);
            }
        }

        private FontStyle? FindStyle(FontFamily family)
        {
            if (family.IsStyleAvailable(FontStyle.Regular)) return FontStyle.Regular;
            if (family.IsStyleAvailable(FontStyle.Bold)) return FontStyle.Bold;
            if (family.IsStyleAvailable(FontStyle.Italic)) return FontStyle.Italic;
            
            return null;
        }

        private void dataGridView1_SelectionChanged(object sender, EventArgs e)
        {
            Form1_Resize(null, null);
        }

        private void dataGridView1_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            DataRowView view = (DataRowView)dataGridView1.Rows[e.RowIndex].DataBoundItem;
            FontInfo.FontListRow row = (FontInfo.FontListRow)view.Row;
            FontFamily family = null;
            foreach (FontFamily tmp in mFonts.Families)
            {
                if (tmp.Name == row.FontName)
                {
                    family = tmp;
                    break;
                }
            }
            mSelectedFamily = family;
            txtFamily.Text = family.Name;
            FontStyle? style = FindStyle(family);
            mSelectedFont = new Font(mSelectedFamily.Name, 20, style.Value, GraphicsUnit.Pixel);
            panel1.Height = (tabControl1.TabPages[1].ClientSize.Height - (txtPreview.Top + txtPreview.Height)) + txtFamily.Top;
            UpdateFontInfo();
            tabControl1.SelectedIndex = 1;
        }

        private void UpdateFontInfo()
        {
            timer1.Stop();

            numFontSize.Value = (decimal)mSelectedFont.Size;

            chkBold.Visible = mSelectedFamily.IsStyleAvailable(FontStyle.Bold);
            chkItalic.Visible = mSelectedFamily.IsStyleAvailable(FontStyle.Italic);

            chkBold.Checked = mSelectedFont.Bold;
            chkItalic.Checked = mSelectedFont.Italic;

            txtPreview.Font = mSelectedFont;

            lblExportPrefix.Text =
                txtFamily.Text.Replace(' ', '_') + "-" +
                (chkBold.Checked ? "B" : "") +
                (chkItalic.Checked ? "I" : "") +
                ((!chkBold.Checked && !chkItalic.Checked) ? "R" : "") + "-" +
                numFontSize.Value.ToString();

            btnExport.Enabled = false;
            Application.DoEvents();

            Form1_Resize(null, null);

            timer1.Interval = 2500;
            timer1.Start();
        }

        private void numFontSize_ValueChanged(object sender, EventArgs e)
        {
            mSelectedFont = new Font(mSelectedFamily.Name, (float)numFontSize.Value, mSelectedFont.Style, GraphicsUnit.Pixel);
            UpdateFontInfo();
        }

        private void chkBold_CheckedChanged(object sender, EventArgs e)
        {
            FontStyle style = (chkBold.Checked ? FontStyle.Bold : FontStyle.Regular) | (chkItalic.Checked ? FontStyle.Italic : FontStyle.Regular);
            mSelectedFont = new Font(mSelectedFamily.Name, (float)numFontSize.Value, style, GraphicsUnit.Pixel);
            UpdateFontInfo();
        }

        private void chkItalic_CheckedChanged(object sender, EventArgs e)
        {
            FontStyle style = (chkBold.Checked ? FontStyle.Bold : FontStyle.Regular) | (chkItalic.Checked ? FontStyle.Italic : FontStyle.Regular);
            mSelectedFont = new Font(mSelectedFamily.Name, (float)numFontSize.Value, style, GraphicsUnit.Pixel);
            UpdateFontInfo();
        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            panel1.Height = (tabControl1.TabPages[1].ClientSize.Height - (txtPreview.Top + txtPreview.Height)) + txtFamily.Top;
            txtPreview.Width = (tabControl1.TabPages[1].ClientSize.Width - txtPreview.Left) - label4.Left;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            timer1.Stop();

            mBitmaps.Clear();
            flowLayoutPanel1.Controls.Clear();

            // first build up a list of valid characters sorted by size (area ??)
            // character indexes grouped by height
            mSizeMap = new Dictionary<int, List<int>>();
            // char code to bitmap
            mCharMap = new Dictionary<int, Bitmap>();
            // upper left pixel offset within bitmap;
            mCharOfs = new Dictionary<int, PointF>();
            // width and height of char (mostly for the width.. the advance distance)
            mCharSize = new Dictionary<int, SizeF>();
            // locations for each char on the texture, including the bounded size of the char
            mCharLoc = new Dictionary<int, RectangleF>();
            // which texture each char is on
            mCharTex = new Dictionary<int, int>();
            // and the list of textures needed to hold all the chars
            mTextures = new List<Bitmap>();

            // create working bitmap for building glyphs
            int lineheight = mSelectedFont.Height;
            
            for (int i = 32; i < 256; i++)
            {
                string c = "" + Convert.ToChar(i);            
                Bitmap image = new Bitmap(128, 128, PixelFormat.Format32bppArgb);
                Graphics g = Graphics.FromImage(image);

                g.FillRectangle(Brushes.Transparent, new Rectangle(0, 0, 128, 128));
                g.DrawString(c, mSelectedFont, Brushes.White, new PointF(0, 0));
                SizeF size = g.MeasureString(c + 'A', mSelectedFont);
                SizeF size2 = g.MeasureString(c + c + 'A', mSelectedFont);
                mCharSize[i] = new SizeF(size2.Width - size.Width, size.Height); // fix for strange xadvance vals
                // figure out extents of character
                int minx=999,maxx=0,miny=999,maxy=0;
                Color mytrans = Color.FromArgb(0);
                for (int x=0; x<size.Width + 4; x++) for (int y=0; y<lineheight; y++)
                {
                    Color pix = image.GetPixel(x, y);
                    if (pix != mytrans)
                    {
                        if (x > maxx) maxx = x;
                        if (x < minx) minx = x;
                        if (y > maxy) maxy = y;
                        if (y < miny) miny = y;
                    }
                }

                int w = (maxx - minx) + 1;
                int h = (maxy - miny) + 1;
                if (w < 0 || w > 512 || h < 0 || h > 512)
                {
                    // if not space, then bad glyph -- skip it
                    if (i != 32) continue;
                    w = 1; h = 1;
                    minx = 1;
                    maxx = 1;
                    miny = 1;
                    maxy = 1;
                }

                if (i == 65) mBaseline = maxy; // baseline is the bottom of a capital A

                mCharOfs[i] = new PointF(minx, miny); // where does pixel data start relative to the origin of the glyph
                w += 2; h += 2; // add one pixel pad all the way around
                Bitmap temp = new Bitmap(w, h, PixelFormat.Format32bppArgb);
                Graphics g2 = Graphics.FromImage(temp);
                g2.DrawImage(image, new Rectangle(1, 1, w, h), new Rectangle(minx, miny, w, h), GraphicsUnit.Pixel);
                mCharMap[i] = temp;
                if (!mSizeMap.ContainsKey(h)) mSizeMap[h] = new List<int>();
                mSizeMap[h].Add(i);
                //PictureBox pb = new PictureBox();
                //pb.Image = temp;
                //pb.Width = temp.Width;
                //pb.Height = temp.Height;
                //flowLayoutPanel1.Controls.Add(pb);
            }
            // get list of heights from largest to smallest
            List<int> keys = mSizeMap.Keys.ToList();
            keys.Sort();
            keys.Reverse();

            int texpix = int.Parse(comboBox1.SelectedItem.ToString());

            while (CharsRemaining(mSizeMap))
            {
                Region reg = new Region(new Rectangle(0, 0, texpix, texpix));
                Bitmap tex = new Bitmap(texpix, texpix, PixelFormat.Format32bppArgb);
                Graphics gtex = Graphics.FromImage(tex);
                gtex.FillRectangle(Brushes.Transparent, new Rectangle(0, 0, 512, 512));

                // start with the tallest chars, and work our way down
                foreach (int h in keys)
                {
                    // while we have chars this tall, keep finding rects to put them in
                    while (mSizeMap[h].Count > 0)
                    {
                        // find width of narrowest char in this group
                        int minw = 999;
                        foreach (int ndx in mSizeMap[h])
                            if (mCharMap[ndx].Width < minw)
                                minw = mCharMap[ndx].Width;

                        // find shortest rectangle in the region that will take these chars
                        RectangleF[] rects = reg.GetRegionScans(new Matrix(1, 0, 0, 1, 0, 0));
                        RectangleF? pick = null;
                        foreach (RectangleF test in rects)
                        {
                            if (test.Height < h) continue;
                            if (test.Width < minw) continue;
                            if (pick == null) pick = test;
                            if (test.Height < pick.Value.Height) pick = test;
                        }

                        // see if there is no more space for chars this size
                        if (!pick.HasValue) break;

                        // find the widest char that will fit the picked region
                        int maxw = 0;
                        int cndx = -1;
                        foreach (int ndx in mSizeMap[h])
                            if (mCharMap[ndx].Width > maxw && mCharMap[ndx].Width <= (int)pick.Value.Width)
                            {
                                maxw = mCharMap[ndx].Width;
                                cndx = ndx;
                            }

                        // and place that char
                        Bitmap bm = mCharMap[cndx];
                        gtex.DrawImage(bm, pick.Value.Location);
                        mCharLoc[cndx] = new RectangleF(pick.Value.Location, new SizeF(bm.Width, bm.Height));
                        mCharTex[cndx] = mTextures.Count;
                        reg.Xor(mCharLoc[cndx]);
                        mSizeMap[h].Remove(cndx);
                    }
                }
                PictureBox p = new PictureBox();
                p.BackColor = Color.Black;
                p.Image = tex;
                p.SizeMode = PictureBoxSizeMode.AutoSize;
                flowLayoutPanel1.Controls.Add(p);
                mTextures.Add(tex);
                Application.DoEvents();
            }
            btnExport.Enabled = true;
        }

        private bool CharsRemaining(Dictionary<int, List<int>> sizemap)
        {
            int cnt = 0;
            foreach (KeyValuePair<int,List<int>> item in sizemap) cnt += item.Value.Count;
            return (cnt > 0);
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (comboBox1.SelectedIndex == -1) return;
            if (mSelectedFont == null) return;
            timer1.Stop();
            timer1_Tick(null, null);
        }

        private void btnExport_Click(object sender, EventArgs e)
        {
            folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyDocuments; // mCurrentPath;

            if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
            {

                mCurrentPath = folderBrowserDialog1.SelectedPath;

                try
                {
                    // Attempt to get a list of security permissions from the folder. 
                    // This will raise an exception if the path is read only or do not have access to view the permissions. 
                    System.Security.AccessControl.DirectorySecurity ds = Directory.GetAccessControl(mCurrentPath);
                }
                catch (UnauthorizedAccessException)
                {
                    MessageBox.Show("You do not have permissions to save files there !!");
                    return;
                }

                // get the textures saved
                int ndx = 0;
                foreach (Bitmap texture in mTextures)
                {
                    string png = Path.Combine(mCurrentPath, lblExportPrefix.Text + "-" + ndx + ".png");
                    texture.Save(png, ImageFormat.Png);
                    ndx++;
                }

                // and now the fnt file
                string fnt = Path.Combine(mCurrentPath, lblExportPrefix.Text + ".fnt");
                StreamWriter file = new StreamWriter(File.Create(fnt, 1024, FileOptions.None));
                // info line
                file.WriteLine(string.Format(
                    "info face=\"{0}\" size={1} bold={2} italic={3} charset=\"{4}\" unicode={5} stretchH={6} smooth={7} aa={8} padding={9} spacing={10} outline={11}",
                    mSelectedFamily.Name, (int)mSelectedFont.GetHeight(), chkBold.Checked ? 1 : 0, chkItalic.Checked ? 1 : 0, "", 1, 100, 1, 1, "0,0,0,0", "1,1", 0
                 )
                 );
                // common line
                int scale = int.Parse(comboBox1.SelectedItem.ToString());
                file.WriteLine(string.Format(
                    "common lineHeight={0} base={1} scaleW={2} scaleH={3} pages={4} packed={5} alphaChnl={6} redChnl={7} greenChnl={8} blueChnl={9}",
                    (int)mSelectedFont.GetHeight(), mBaseline, scale, scale, mBitmaps.Count, 1, 1, 0, 0, 0
                    )
                    );
                ndx = 0;
                foreach (Bitmap texture in mBitmaps)
                {
                    string png = lblExportPrefix.Text + "-" + ndx + ".png";
                    file.WriteLine(string.Format(
                        "page id={0} file=\"{1}\"",
                        ndx, png
                        )
                        );
                    ndx++;
                }
                file.WriteLine(string.Format(
                    "chars count={0}",
                    mCharMap.Count
                    )
                    );
                for (int i = 32; i < 256; i++)
                {
                    if (!mCharMap.ContainsKey(i)) continue;
                    file.WriteLine(string.Format(
                        "char id={0} x={1} y={2} width={3} height={4} xoffset={5} yoffset={6} xadvance={7} page={8} chnl={9}",
                        i,
                        (int)mCharLoc[i].X + 1, (int)mCharLoc[i].Y + 1,
                        (int)mCharLoc[i].Width - 2, (int)mCharLoc[i].Height - 2,
                        (int)mCharOfs[i].X, (int)mCharOfs[i].Y,
                        (int)mCharSize[i].Width,
                        mCharTex[i], 15
                        )
                        );
                }
                file.Close();
            }
        }
    }
}
